import celery
import json
import rdflib
import requests
import requests_http_signature

from .. import activitypub
from .. import feeds
from .. import model
from .. import settings
from . import broker_url
from . import broker
from . import database_session

log = celery.utils.log.get_task_logger(__name__)
log.setLevel(settings.LOG_LEVEL)

# The following is a decorator that accepts a dictionary as input and adds it to
# _PATTERNS_ together with the decorated function. The dictionary is used as a
# pattern for matching incoming Activities. If an incoming Activity matches one
# of the patters in _PATTERNS_, then the corresponding function is executed.
_PATTERNS_ = []
def pattern(activity_pattern):
    def closure(func):
        
        def decorator(*args, **kwargs):
            func(*args, **kwargs)
        
        global _PATTERNS_
        _PATTERNS_.append((activity_pattern, decorator))
        
        return decorator
    return closure

@broker.task
def perform(actor_uri, activity):
    """
    This task is responsible for accepting an incoming Activity after it's been
    validated (see tasks.delivery) and decide what to do with it.
    
    :param actor_uri: URI of the Actor that has received the Activity
    :param activity: the Activity that was sent to the Actor
    """
    
    activity = activitypub.Activity(activity)
    activity.node('actor')
    activity.node('object')
    
    with database_session() as database:
        
        # Recreate the actor class from its URI
        actor = model.from_uri(database, actor_uri)
        
        if not actor:
            log.debug('Actor {} doesn\'t exist. Ignoring incoming Activity.'.format(actor_uri))
            return
        
        for activity_pattern, function in _PATTERNS_:
            if activity.match(activity_pattern):
                return function(database, actor, activity)
        
        log.debug('Activity {} did not match any pattern. Ignoring incoming Activity.'.format(activity['id']))

@pattern({
    'type': 'Follow',
    'id': {},
    'actor': {
        'id': {}
    },
    'object': {
        # 'type': 'Person',
        'id': {}
    },
})
def follow(database, actor, activity):
        
    log.debug('Handling incoming Follow Activity.')
    
    # A Follow request should be sent to the right Actor
    if activity['object']['id'] != actor.local_uri:
        log.debug('Ignoring Activity {}. Actor {} has received a Follow request addressed to Actor {}' \
                  .format(activity['id'], actor.local_uri, activity['object']['id']))
        return
    
    # Check if the local actor is already following the remote actor
    if database.query(
            database.query(model.Collection) \
                    .filter(model.Collection.uri == actor.following_uri) \
                    .filter(model.Collection.item == activity['actor']['id']) \
                    .exists()
        ).scalar():
        
        log.info('Actor {} is already following {}. Ignoring Follow request.'.format(actor.local_uri, activity['actor']['id']))
        return
    
    # Add the remote actor to our followers collection
    database.add(model.Collection(uri  = actor.followers_uri,
                                  item = activity['actor']['id']))
    
    # Add a feed
    database.add(model.Feed(
        actor_uri = actor.local_uri,
        content = json.dumps(feeds.follow(activity['actor'], actor.jsonld))
    ))
    
    # Automatically accept Follow requests and add the remote actor to the
    # following collection
    database.add(model.Collection(uri  = actor.following_uri,
                                  item = activity['actor']['id']))
    
    # Add a feed
    database.add(model.Feed(
        actor_uri = actor.local_uri,
        content = json.dumps(feeds.follow(actor.jsonld, activity['actor']))
    ))
    
    # Cache a copy of the remote actor for quick lookups
    database.merge(model.Resource(
        uri = activity['actor']['id'],
        document = json.dumps(activity['actor'])))
    
    # Commit before sending the Accept Activity
    database.commit()
    
    # Create and send Accept Activity
    activitypub.Accept(actor  = actor.uri,
                       object = activity['id']) \
               .distribute()

@pattern({
    'type': 'Accept',
    'id': {},
    'actor': {
        'id': {}
    },
    'object': {
        'type': 'Follow',
        'id': {},
        'actor': {},
        'object': {}
    },
})
def accept_follow(database, actor, activity):
    """
    Accept of a Follow request.
    """
    
    log.debug('Handling incoming Accept(Follow) Activity.')
    
    follow_activity = activity['object']
    follow_activity.node('actor')
    follow_activity.node('object')
    
    if actor.local_uri == follow_activity['actor']['id'] \
    and follow_activity['object']['id'] == activity['actor']['id']:
        
        log.debug('{} has accepted follow request from {}'.format(
            activity['actor']['id'], actor.local_uri))
        
        # Now that the request was accepted, we add the actor to the "followers" list
        database.add(model.Collection(uri  = actor.followers_uri,
                                      item = activity['actor']['id']))
        
        # Add a feed
        database.add(model.Feed(
            actor_uri = actor.local_uri,
            content = json.dumps(feeds.follow(activity['actor'], actor.jsonld))
        ))
    
    database.commit()



